/**
  Contains implementation for UDP Client 
 */

#include "globals.h"
datagram_list packet_buffer_head;
pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
int mu;

void *read_buffer();

int main ()
{

    char ip_client [INET_ADDRSTRLEN]; 
    char ip_server [INET_ADDRSTRLEN];
    char filename [TMP_BUFSIZE]; 
    char buf [MAX_DATAGRAM_BUF_SIZE];
    char * ptr;

    int server_port;
    int win;
    int seed;
    int i=1;
    int j;
    int sockfd;
    int flags = 0;
    int err;

    float p;

    FILE * fp;

    struct ifi_info	*ifi;
    struct ifi_info *ifihead;	

    struct sockaddr * sa;
    struct sockaddr_in cliaddr;
    struct sockaddr_in servaddr;
    struct sockaddr_in sock_nm;	

    socklen_t len;	

    char ip_addr[INET_ADDRSTRLEN];
    char net_msk[INET_ADDRSTRLEN];
    char sub_msk[INET_ADDRSTRLEN];
    char check_sub_msk[INET_ADDRSTRLEN];

    pthread_t tid;
    
    datagram dgram, *dtemp;

    packet_buffer_head.link = 0;
    packet_buffer_head.dgram = (datagram *) malloc(sizeof(datagram));
    packet_buffer_head.dgram->seq_num = -1;
    packet_buffer_head.dgram->ack_num = -1;

    parse_client_in(ip_server, &server_port, filename, &win, &seed, &p, &mu);

    for ( ifihead = ifi = Get_ifi_info_plus( AF_INET, 1 ), i = 0; ifi != NULL; ifi = ifi->ifi_next, i++) 
    {
        bzero(ip_addr, INET_ADDRSTRLEN);
        bzero(net_msk, INET_ADDRSTRLEN);
        bzero(sub_msk, INET_ADDRSTRLEN);

        sa = ifi->ifi_addr;
        strcpy( ip_addr, Sock_ntop_host( sa, sizeof( *sa ) ) );
        sa =  ifi->ifi_ntmaddr;
        strcpy( net_msk, Sock_ntop_host( sa, sizeof( *sa ) ) );

        get_subnet_mask(ip_addr, net_msk, sub_msk);

        printf( "%s Client Interface %d:\n\tIP Address :: %s\n\tNetwork Mask :: %s\n\tSubnet Mask :: %s\n\n", 
                CLI_LOG, (i+1), ip_addr, net_msk, sub_msk );

        if( strcmp( ip_server, ip_addr ) == 0 )
        {
            flags = MSG_DONTROUTE;
            break;
        }

    }
    free_ifi_info_plus(ifihead);
    
    if(flags == MSG_DONTROUTE)
    {
            //
            // Client and Server on the same host		
            //			
            strcpy( ip_server, "127.0.0.1" );
            strcpy( ip_client, "127.0.0.1" );
            printf( "%s Client and Server are on the same host\n\tIPclient :: %s IPserver :: %s \n", 
                    CLI_LOG, ip_client, ip_server );
    }
    else
    {
  	  	for ( ifihead = ifi = Get_ifi_info_plus( AF_INET, 1 ), i = 0; ifi != NULL; ifi = ifi->ifi_next, i++) 
    		{
			bzero(ip_addr, INET_ADDRSTRLEN);
			bzero(net_msk, INET_ADDRSTRLEN);
			bzero(sub_msk, INET_ADDRSTRLEN);

			sa = ifi->ifi_addr;
			strcpy( ip_addr, Sock_ntop_host( sa, sizeof( *sa ) ) );
			sa =  ifi->ifi_ntmaddr;
			strcpy( net_msk, Sock_ntop_host( sa, sizeof( *sa ) ) );

			get_subnet_mask(ip_addr, net_msk, sub_msk);

			if( strcmp( ip_addr, "127.0.0.1" ) != 0 )
			{
		

			    //
			    // Check if Server is local to Client i.e. if 
			    // Client and Server share the same subnet mask
			    //
		
			    bzero(check_sub_msk, INET_ADDRSTRLEN);
			    get_subnet_mask(ip_server, net_msk, check_sub_msk);

			    if( strcmp( check_sub_msk, sub_msk ) == 0 )
			    {
				strcpy( ip_client, ip_addr );
				printf("%s Client and Server are local i.e. in the same subnet\n\tIPclient :: %s IPserver :: %s\n", 
				        CLI_LOG, ip_client, ip_server);
				flags = MSG_DONTROUTE;        
				break;
			    }
			}
			if(ifi->ifi_next == NULL )		
			{
			    //
			    // Client and Server are neither local nor on the same host
			    //		

			    strcpy( ip_client, ip_addr );
			    printf( "%s Client and Server are not local\n\tIPclient :: %s IPserver :: %s \n", 
				    CLI_LOG, ip_client, ip_server );
			}
		
	   	  }    

	    	  free_ifi_info_plus(ifihead);
	}    

    //
    // Creating a UDP socket and Binding with the Client's IP 
    // and a ephemeral port chosen by the Kernel
    //

    sockfd = Socket( AF_INET, SOCK_DGRAM, 0 );
    bzero( &cliaddr, sizeof( cliaddr ) );
    cliaddr.sin_family = AF_INET;
    inet_pton( AF_INET, ip_client, &cliaddr.sin_addr );
    cliaddr.sin_port = htons( 0 );

    Bind( sockfd, ( SA * ) &cliaddr, sizeof( cliaddr ) );

    len = sizeof( sock_nm );
    getsockname( sockfd, ( SA * ) &sock_nm, &len );

    printf("%s Client Socket created and Binded\n\tIPClient:Portno : %s \n", 
            CLI_LOG, Sock_ntop( ( SA * ) &sock_nm, len ) );

    //
    // Connecting the socket by specifying Server's IP 
    // and well known port using UDP Connect
    //

    bzero( &servaddr, sizeof( servaddr ) );
    servaddr.sin_family = AF_INET;
    inet_pton( AF_INET, ip_server, &servaddr.sin_addr );
    servaddr.sin_port = htons( server_port );

    Connect( sockfd, ( SA * ) &servaddr, sizeof( servaddr ) );

    bzero( &sock_nm, sizeof( sock_nm ) );
    len = sizeof( sock_nm );
    getpeername( sockfd, ( SA * ) &sock_nm, &len );

//    send_datagram (sockfd, filename, flags, &packet_buffer_head, QFLAG_DONTQ);
    dtemp = (datagram*)malloc(sizeof(datagram));
    strcpy(dtemp->data, filename);
    dtemp->wsize = win;
    send(sockfd, dtemp, sizeof(*dtemp), flags);

    printf( "%s Request sent for file: '%s'\n", CLI_LOG, filename );

    bzero(buf, MAX_DATAGRAM_BUF_SIZE);
    recv_datagram(sockfd, buf, flags);
//    log_msg(buf);
    ptr=strchr( buf, ':' );

    if (ptr == NULL)
    {
   //     log_msg("ptr is null");
        printf("Server not found / File %s not found on the Server \n", filename);
        return 0;
    }

    strcpy( buf, ( ptr+1 ) );

    //
    // Client Reconnects to Server on the new Server port (the ephemeral port)
    //

    servaddr.sin_port = htons( atoi( buf ) );
    

    Connect( sockfd, (SA*) &servaddr, sizeof(servaddr) );

    printf("%s New connection established with Server at Port number: %s\n", 
            CLI_LOG, buf);

    strcpy (buf, "ACK" );
//    send_datagram (sockfd, buf, flags, &packet_buffer_head, QFLAG_DONTQ);
    dtemp = (datagram *) malloc(sizeof(datagram)) ;
    strcpy(dtemp->data, filename);
    dtemp->wsize = win;
    send(sockfd, dtemp, sizeof(*dtemp), flags);
    printf("%s ACK sent to Server\n", CLI_LOG);
        log_msg("before recv");
	
        Pthread_create(&tid, NULL, &read_buffer, NULL);
        log_msg("Thread created");

        dg_recv_datagram(sockfd, buf, flags, &packet_buffer_head, win,
                seed, &p, &buffer_mutex);
        Pthread_join(tid, NULL);

    close( sockfd );


    return( 0 );
}

void *read_buffer()
{
    int last_ack;
	double rand_sleep;
    srand (time(NULL));
    int sleep_val;

    datagram_list *temp = (datagram_list *)malloc(sizeof(datagram_list));
    datagram * find_dg = NULL, *inorder;

    int to_find, ix;
    temp =  packet_buffer_head.link;	
    last_ack = -1;  	

    log_msg("thread entry");

    while (1) 
    {
        log_msg("while start");

        temp =  packet_buffer_head.link;
        
        if(temp != 0)		
        {
            Pthread_mutex_lock(&buffer_mutex);
            printf("Child thread locked the shared Buffer for Reading\n");

            ix = packet_buffer_head.dgram->ack_num;
            inorder = get_highest_in_order(&packet_buffer_head);

            while(ix < (inorder->ack_num))
            {
                to_find = packet_buffer_head.dgram->ack_num + 1;
                find_dg = find_datagram_ack(&packet_buffer_head, to_find);
                if ( find_dg != NULL)
                {
                    printf("Child Reads:-\n %s \n", find_dg->data);
                    
                    if (find_dg->data[0] == EOF)
                    {
                        printf("Child thread unlocked the shared Buffer\n");
                        Pthread_mutex_unlock(&buffer_mutex);
                        pthread_exit(NULL);
                    }

                    packet_buffer_head.dgram->ack_num = to_find;
                }

                temp=temp->link;
                ix++;
            }			
            printf("Child thread unlocked the shared Buffer\n");
            Pthread_mutex_unlock(&buffer_mutex);
        }    		
       
        sleep_val = (-1 * (mu/1000) * log(((rand() % 101 )/ 100.0)));
        sleep_val = sleep_val % 10;
       
        //
        // Sleeping for at least 3 seconds.
        //

        if (sleep_val < 3)
        {
            sleep_val = 3;
        }

        log_msg_int("sleeping for", sleep_val);

        sleep(sleep_val);
    }
    
    pthread_exit(NULL);
}

void
parse_client_in(
        char ip_server[],
        int * server_port,
        char filename[],
        int * win,
        int * seed,
        float * p,
        int * mu
        )
{

    FILE* fp;
    char buf [TMP_BUFSIZE] = "\0";

    int flags;
    int i ;
    int j;

    struct in_addr ipv4_addr;

    if ((fp = fopen("client.in","r")) == NULL)
    {
        printf( "%s client.in not found\n", CLI_LOG);
        exit( 1 );
    }
    else
    {
        i = 1;

        while ( Fgets ( buf, TMP_BUFSIZE, fp ) != NULL )
        {
            flags=0;

            if( i != 1 && i != 3 )
            {
                for( j = 0; buf[j] != '\0'; j++ )
                {
                    if( isdigit ( buf[j] ) )
                    {
                        continue;
                    }	
                    else if( flags == -1 )
                    {
                        break;
                    }	
                    else if( i==6 && buf[j] == '.' )
                    {
                        flags = -1;					
                    }
                    else 
                    {
                        break;	
                    }
                }

                if( j != strlen( buf )-1 )
                {
                    flags = 1;
                }
            }

            switch(i)
            {
                case 1: 
                    strcpy( ip_server, buf );
                    ip_server[ strlen(buf) - 1 ] = '\0';

                    if( inet_pton ( AF_INET, ip_server, &ipv4_addr.s_addr ) <= 0 )
                    {
                        printf( "%s Error client.in :: Line 1 :: contains invalid Server IP\n", CLI_LOG);
                        exit( 1 );
                    }	

                    i++;
                    break;
                case 2:
                    if( flags || ( *server_port = atoi(buf) ) == 0 )
                    {
                        printf( "%s Error client.in :: Line 2 :: Invalid Port number\n", CLI_LOG );
                        exit( 1 );
                    }	

                    i++;
                    break;
                case 3:
                    strcpy( filename, buf );
                    filename[ strlen(buf)-1 ] = '\0';
                    i++;
                    break;	
                case 4:
                    if( flags || ( *win=atoi(buf) ) == 0 )
                    {
                        printf( "%s Error client.in :: Line 4 :: Invalid Receiving Sliding Window Size\n", CLI_LOG );
                        exit( 1 );
                    }	

                    i++;
                    break;	
                case 5:
                    if( flags || ( *seed = atoi(buf) ) == 0 )
                    {
                        printf( "%s Error client.in :: Line 5 :: Invalid Seed value\n", CLI_LOG );
                        exit( 1 );
                    }	

                    i++;					
                    break;	
                case 6:
                    *p=atof( buf );

                    if( flags == 1 || *p < 0.0 || *p > 1.0 )
                    {
                        printf( "%s Error client.in :: Line 6 :: Invalid Probability value\n", CLI_LOG );
                        exit( 1 );
                    }	

                    i++;									
                    break;	
                case 7:
                    if( flags || ( *mu = atoi(buf) ) == 0 )
                    {
                        printf( "%s Error client.in :: Line 7 :: Invalid Mean value\n", CLI_LOG );
                        exit( 1 );
                    }	

                    i++;								
                    break;	
            }

            bzero(buf, TMP_BUFSIZE);            
        }

        fclose(fp);
    }
}


